package edu.mercer.sse.database;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import com.google.common.base.Optional;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableBiMap;

import edu.mercer.sse.errors.CriticalException;
import edu.mercer.sse.errors.CustomLogger;
import edu.mercer.sse.errors.LoggerCustom;
import edu.mercer.sse.app.Config;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;

/**
 * 
 * Database query fields or literal values and corresponding LDAP keys.<p>
 *
 * Example:
 * <table border=1>
 *  <tr><td> Query column </td><td> LDAP key        </td></tr>
 * 	<tr><td> firstname    </td><td> cn              </td></tr>
 * 	<tr><td> lastname     </td><td> sn              </td></tr>
 * 	<tr><td> user_id      </td><td> uid             </td></tr>
 * 	<tr><td> phone        </td><td> telephoneNumber </td></tr>
 * 
 * 	<tr><td> '100'        </td><td> gidNumber       </td></tr>
 * 
 * 	<tr><td colspan=2> <u>May contain literals</u> instead of  
 * 	named-columns. Literals enclosed in single quotes. </td></tr>
 * 
 * </table>
 */

public class DB2LDAPLayout
{
	private static DBConnector _database = new DBConnector();

	private static Optional<BiMap<String,String>> _ldapRecSpec = Optional
			.absent();
	
	private static Boolean _verbose = Config.settings()
			 						 		.getProperty("verbose")
			 						 		.equalsIgnoreCase("true");

	private static final CustomLogger _log = LoggerCustom.obtainLog( 
			"DB2LDAPLayout", Config.getLogLevel());	
	
	
	/**
	 * No-arg constructor.<p>
	 */
	
	public DB2LDAPLayout()
	{
		
	}//constructor

	
	public DB2LDAPLayout( final DBConnector dbconn )
	{
		withDBConnector( dbconn );
		
	}//constructor
	
	
	public static void withDBConnector( final DBConnector dbconn )
	{
		checkNotNull( dbconn, "null DBConnector" );
		
		_database = dbconn;
		
	}//method
	
	
	/**
	 * Convert database column name into corresponding LDAP key as specified  
	 * 	in database->LDAP table layout.<p>
	 * 
	 * Convenience method for BiMap functionality of layout.<p>
	 * 
	 * @param dbColumnName - String database column name
	 * 
	 * @return  String LDAP key name
	 * 
	 * @throws SQLException - database or network error
	 */
	
	public static String dbColAsLDAPKey( final String dbColumnName ) 
			throws SQLException 
	{
		checkNotNull( dbColumnName, "null database column name" );
		
		checkArgument( dbColumnName.length() > 0, "empty database column " 
				+ "name" );
		
		/* may be called by mistake using an LDAP key, tolerate */
		
		if( getLayout().containsValue(dbColumnName) )
		{ 
			_log.warning( "dbColAsLDAPKey: " + dbColumnName + " is not a "
					+ "database column, returning unchanged" );
			
			return dbColumnName;
			
		}//if
		
		checkArgument( getLayout().containsKey(dbColumnName), 
				"invalid column name: " + dbColumnName );
		
		return getLayout().get(dbColumnName);
		
	}//method


	/**
	 * Convert LDAP key name into corresponding database column name as 
	 * 	specified in database->LDAP table layout.<p>
	 * 
	 * Convenience method for BiMap functionality of layout.<p>
	 * 
	 * @param ldapKey - String LDAP key name
	 * 
	 * @return  String database column name
	 * 
	 * @throws SQLException - database or network error
	 */
	
	public static String ldapKeyAsdbCol( final String ldapKey ) 
			throws SQLException 
	{
		checkNotNull( ldapKey, "null ldap key name" );
		
		checkArgument( ldapKey.length() > 0, "empty ldap key " 
				+ "name" );
		
		checkArgument( getLayout().containsValue(ldapKey), 
				"invalid ldap key name: " + ldapKey );
		
		return getLayout().inverse().get(ldapKey);
		
	}//method
	
	
	/**
	 * Get Database -> LDAP record layout.<p>
	 * 
	 * @return  ImmutableBiMap&#60;String,String&#62; query item, ldap item
	 * 
	 * <br> Example:
	 * <ul>
	 * 	<li>	firstname, cn
	 * 	<li>	lastname, sn
	 * 	<li>	user_id, uid
	 * 	<li>	phone, telephoneNumber
	 * 
	 * 	<li>	'100', gidNumber - <u>May contain literals</u> instead of  
	 * 	named-columns. Literals enclosed in single quotes.
	 * 
	 * </ul>
	 * 
	 * @throws SQLException
	 * 
	 * @see #loadLayout()
	 */
	
	public static ImmutableBiMap<String,String> getLayout() throws 
		SQLException
	{
		return ImmutableBiMap.copyOf( _ldapRecSpec.or( loadLayout() ) );
		
	}//method
	

	/**
	 * Get Database -> LDAP record layout.<p>
	 * 
	 * @return  BiMap&#60;String,String&#62; query item, ldap item
	 * 
	 * @throws SQLException
	 * 
	 * @see #getLayout()
	 */
	
	private static BiMap<String,String> loadLayout() throws SQLException
	{
		if( _ldapRecSpec.isPresent() ) return _ldapRecSpec.get();
		
		final Statement layoutQryState = _database.con().createStatement();
		
		layoutQryState.executeQuery("SELECT * FROM `" +	
		
				Config.settings().getProperty(
						"database-table-ldap-layout") 
				
				+ "` LIMIT " 

				+ Config.settings().getProperty(
						"database-table-ldap-layout-limit"));
		
		final ResultSet recs = layoutQryState.getResultSet();

		//BiMap allows inversion, dynamic access by value
		
		final BiMap<String,String> ldapLayout = HashBiMap.create();
				
		while( recs.next() )
		{
			if( _verbose ) _log.fine( recs.getString("qry_item") + " == " + 
						    		  recs.getString("ldap_item") );
			
			ldapLayout.put( recs.getString("qry_item"), 
							recs.getString("ldap_item") );
			
		}//for
		
		if( ldapLayout.size() < 1 ) throw new CriticalException(
				"Fail find ldap layout in database");
		
		_ldapRecSpec = Optional.of( ldapLayout );
		
		return ldapLayout;
		
	}//method
	
	
}//class
